home *** CD-ROM | disk | FTP | other *** search
/ InfoMagic Internet Tools 1993 July / Internet Tools.iso / RockRidge / mail / pine / imap-3.0 / ANSI / c-client / nntpclient.c < prev    next >
Encoding:
C/C++ Source or Header  |  1993-07-25  |  42.7 KB  |  1,416 lines

  1. /*
  2.  * Program:    Network News Transfer Protocol (NNTP) client routines
  3.  *
  4.  * Author:    Mark Crispin
  5.  *        Networks and Distributed Computing
  6.  *        Computing & Communications
  7.  *        University of Washington
  8.  *        Administration Building, AG-44
  9.  *        Seattle, WA  98195
  10.  *        Internet: MRC@CAC.Washington.EDU
  11.  *
  12.  * Date:    5 January 1993
  13.  * Last Edited:    26 July 1993
  14.  *
  15.  * Copyright 1993 by the University of Washington.
  16.  *
  17.  *  Permission to use, copy, modify, and distribute this software and its
  18.  * documentation for any purpose and without fee is hereby granted, provided
  19.  * that the above copyright notices appear in all copies and that both the
  20.  * above copyright notices and this permission notice appear in supporting
  21.  * documentation, and that the name of the University of Washington not be
  22.  * used in advertising or publicity pertaining to distribution of the software
  23.  * without specific, written prior permission.  This software is made
  24.  * available "as is", and
  25.  * THE UNIVERSITY OF WASHINGTON DISCLAIMS ALL WARRANTIES, EXPRESS OR IMPLIED,
  26.  * WITH REGARD TO THIS SOFTWARE, INCLUDING WITHOUT LIMITATION ALL IMPLIED
  27.  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, AND IN
  28.  * NO EVENT SHALL THE UNIVERSITY OF WASHINGTON BE LIABLE FOR ANY SPECIAL,
  29.  * INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
  30.  * FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, TORT
  31.  * (INCLUDING NEGLIGENCE) OR STRICT LIABILITY, ARISING OUT OF OR IN
  32.  * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  33.  *
  34.  */
  35.  
  36.  
  37. #include "mail.h"
  38. #include "osdep.h"
  39. #include <ctype.h>
  40. #include <stdio.h>
  41. #include <netdb.h>
  42. #include <errno.h>
  43. extern int errno;        /* just in case */
  44. #include <sys/file.h>
  45. #include <sys/stat.h>
  46. #include "smtp.h"
  47. #define search_t Search_t
  48. #include "news.h"
  49. #undef search_t
  50. #undef LOCAL
  51. #include "nntp.h"
  52. #include "nntpclient.h"
  53. #include "rfc822.h"
  54. #include "misc.h"
  55.  
  56. /* NNTP mail routines */
  57.  
  58.  
  59. /* Driver dispatch used by MAIL */
  60.  
  61. DRIVER nntpdriver = {
  62.   "nntp",            /* driver name */
  63.   (DRIVER *) NIL,        /* next driver */
  64.   nntp_valid,            /* mailbox is valid for us */
  65.   nntp_parameters,        /* manipulate parameters */
  66.   nntp_find,            /* find mailboxes */
  67.   nntp_find_bboards,        /* find bboards */
  68.   nntp_find_all,        /* find all mailboxes */
  69.   nntp_find_all_bboards,    /* find all bboards */
  70.   nntp_subscribe,        /* subscribe to mailbox */
  71.   nntp_unsubscribe,        /* unsubscribe from mailbox */
  72.   nntp_subscribe_bboard,    /* subscribe to bboard */
  73.   nntp_unsubscribe_bboard,    /* unsubscribe from bboard */
  74.   nntp_create,            /* create mailbox */
  75.   nntp_delete,            /* delete mailbox */
  76.   nntp_rename,            /* rename mailbox */
  77.   nntp_mopen,            /* open mailbox */
  78.   nntp_close,            /* close mailbox */
  79.   nntp_fetchfast,        /* fetch message "fast" attributes */
  80.   nntp_fetchflags,        /* fetch message flags */
  81.   nntp_fetchstructure,        /* fetch message envelopes */
  82.   nntp_fetchheader,        /* fetch message header only */
  83.   nntp_fetchtext,        /* fetch message body only */
  84.   nntp_fetchbody,        /* fetch message body section */
  85.   nntp_setflag,            /* set message flag */
  86.   nntp_clearflag,        /* clear message flag */
  87.   nntp_search,            /* search for message based on criteria */
  88.   nntp_ping,            /* ping mailbox to see if still alive */
  89.   nntp_check,            /* check for new messages */
  90.   nntp_expunge,            /* expunge deleted messages */
  91.   nntp_copy,            /* copy messages to another mailbox */
  92.   nntp_move,            /* move messages to another mailbox */
  93.   nntp_append,            /* append string message to mailbox */
  94.   nntp_gc            /* garbage collect stream */
  95. };
  96.  
  97.                 /* prototype stream */
  98. MAILSTREAM nntpproto = {&nntpdriver};
  99.  
  100. /* NNTP mail validate mailbox
  101.  * Accepts: mailbox name
  102.  * Returns: our driver if name is valid, NIL otherwise
  103.  */
  104.  
  105. DRIVER *nntp_valid (char *name)
  106. {
  107.                 /* must be bboard */
  108.   return *name == '*' ? mail_valid_net (name,&nntpdriver,NIL,NIL) : NIL;
  109. }
  110.  
  111.  
  112. /* News manipulate driver parameters
  113.  * Accepts: function code
  114.  *        function-dependent value
  115.  * Returns: function-dependent return value
  116.  */
  117.  
  118. void *nntp_parameters (long function,void *value)
  119. {
  120.   fatal ("Invalid nntp_parameters function");
  121.   return NIL;
  122. }
  123.  
  124. /* NNTP mail find list of mailboxes
  125.  * Accepts: mail stream
  126.  *        pattern to search
  127.  */
  128.  
  129. void nntp_find (MAILSTREAM *stream,char *pat)
  130. {
  131.   /* Always a no-op */
  132. }
  133.  
  134.  
  135. /* NNTP mail find list of bboards
  136.  * Accepts: mail stream
  137.  *        pattern to search
  138.  */
  139.  
  140. void nntp_find_bboards (MAILSTREAM *stream,char *pat)
  141. {
  142.   void *s = NIL;
  143.   char *t,*u,*bbd,*patx,tmp[MAILTMPLEN];
  144.   if (stream) {            /* use .newsrc if a stream given */
  145.                 /* begin with a host specification? */
  146.     if (((*pat == '{') || ((*pat == '*') && (pat[1] == '{'))) &&
  147.     (t = strchr (pat,'}')) && *(patx = ++t)) {
  148.       if (*pat == '*') pat++;    /* yes, skip leading * (old Pine behavior) */
  149.       strcpy (tmp,pat);        /* copy host name */
  150.       bbd = tmp + (patx - pat);    /* where we write the bboards */
  151.     }
  152.     else {            /* no host specification */
  153.       bbd = tmp;        /* no prefix */
  154.       patx = pat;        /* use entire specification */
  155.     }
  156.     while (t = news_read (&s)) if (u = strchr (t,':')) {
  157.       *u = '\0';        /* tie off at end of name */
  158.       if (pmatch (t,patx)) {    /* pattern match */
  159.     strcpy (bbd,t);        /* write newsgroup name after prefix */
  160.     mm_bboard (tmp);
  161.       }
  162.     }
  163.   }
  164. }
  165.  
  166. /* NNTP mail find list of all mailboxes
  167.  * Accepts: mail stream
  168.  *        pattern to search
  169.  */
  170.  
  171. void nntp_find_all (MAILSTREAM *stream,char *pat)
  172. {
  173.   /* Always a no-op */
  174. }
  175.  
  176.  
  177. /* NNTP mail find list of all bboards
  178.  * Accepts: mail stream
  179.  *        pattern to search
  180.  */
  181.  
  182. void nntp_find_all_bboards (MAILSTREAM *stream,char *pat)
  183. {
  184.   char *s,*t,*bbd,*patx,tmp[MAILTMPLEN];
  185.                 /* use .newsrc if a stream given */
  186.   if (stream && LOCAL && LOCAL->nntpstream) {
  187.                 /* begin with a host specification? */
  188.     if (((*pat == '{') || ((*pat == '*') && (pat[1] == '{'))) &&
  189.     (t = strchr (pat,'}')) && *(patx = ++t)) {
  190.       if (*pat == '*') pat++;    /* yes, skip leading * (old Pine behavior) */
  191.       strcpy (tmp,pat);        /* copy host name */
  192.       bbd = tmp + (patx - pat);    /* where we write the bboards */
  193.     }
  194.     else {            /* no host specification */
  195.       bbd = tmp;        /* no prefix */
  196.       patx = pat;        /* use entire specification */
  197.     }
  198.                 /* ask server for all active newsgroups */
  199.     if (!(smtp_send (LOCAL->nntpstream,"LIST","ACTIVE") == NNTPGLIST)) return;
  200.                 /* process data until we see final dot */
  201.     while ((s = tcp_getline (LOCAL->nntpstream->tcpstream)) && *s != '.') {
  202.                 /* tie off after newsgroup name */
  203.       if (t = strchr (s,' ')) *t = '\0';
  204.       if (pmatch (s,patx)) {    /* report to main program if have match */
  205.     strcpy (bbd,s);        /* write newsgroup name after prefix */
  206.     mm_bboard (tmp);
  207.       }
  208.       fs_give ((void **) &s);    /* clean up */
  209.     }
  210.   }
  211. }
  212.  
  213. /* NNTP mail subscribe to mailbox
  214.  * Accepts: mail stream
  215.  *        mailbox to add to subscription list
  216.  * Returns: T on success, NIL on failure
  217.  */
  218.  
  219. long nntp_subscribe (MAILSTREAM *stream,char *mailbox)
  220. {
  221.   return NIL;            /* never valid for NNTP */
  222. }
  223.  
  224.  
  225. /* NNTP mail unsubscribe to mailbox
  226.  * Accepts: mail stream
  227.  *        mailbox to delete from subscription list
  228.  * Returns: T on success, NIL on failure
  229.  */
  230.  
  231. long nntp_unsubscribe (MAILSTREAM *stream,char *mailbox)
  232. {
  233.   return NIL;            /* never valid for NNTP */
  234. }
  235.  
  236.  
  237. /* NNTP mail subscribe to bboard
  238.  * Accepts: mail stream
  239.  *        bboard to add to subscription list
  240.  * Returns: T on success, NIL on failure
  241.  */
  242.  
  243. long nntp_subscribe_bboard (MAILSTREAM *stream,char *mailbox)
  244. {
  245.   char *s = strchr (mailbox,'}');
  246.   return s ? news_subscribe_bboard (stream,s+1) : NIL;
  247. }
  248.  
  249.  
  250. /* NNTP mail unsubscribe to bboard
  251.  * Accepts: mail stream
  252.  *        bboard to delete from subscription list
  253.  * Returns: T on success, NIL on failure
  254.  */
  255.  
  256. long nntp_unsubscribe_bboard (MAILSTREAM *stream,char *mailbox)
  257. {
  258.   char *s = strchr (mailbox,'}');
  259.   return s ? news_unsubscribe_bboard (stream,s+1) : NIL;
  260. }
  261.  
  262. /* NNTP mail create mailbox
  263.  * Accepts: mail stream
  264.  *        mailbox name to create
  265.  * Returns: T on success, NIL on failure
  266.  */
  267.  
  268. long nntp_create (MAILSTREAM *stream,char *mailbox)
  269. {
  270.   return NIL;            /* never valid for NNTP */
  271. }
  272.  
  273.  
  274. /* NNTP mail delete mailbox
  275.  *        mailbox name to delete
  276.  * Returns: T on success, NIL on failure
  277.  */
  278.  
  279. long nntp_delete (MAILSTREAM *stream,char *mailbox)
  280. {
  281.   return NIL;            /* never valid for NNTP */
  282. }
  283.  
  284.  
  285. /* NNTP mail rename mailbox
  286.  * Accepts: mail stream
  287.  *        old mailbox name
  288.  *        new mailbox name
  289.  * Returns: T on success, NIL on failure
  290.  */
  291.  
  292. long nntp_rename (MAILSTREAM *stream,char *old,char *new)
  293. {
  294.   return NIL;            /* never valid for NNTP */
  295. }
  296.  
  297. /* NNTP mail open
  298.  * Accepts: stream to open
  299.  * Returns: stream on success, NIL on failure
  300.  */
  301.  
  302. MAILSTREAM *nntp_mopen (MAILSTREAM *stream)
  303. {
  304.   long i,j,k;
  305.   long nmsgs = 0;
  306.   long recent = 0;
  307.   long unseen = 0;
  308.   char c = NIL,*s,*t,tmp[MAILTMPLEN];
  309.   NETMBX mb;
  310.   void *sdb = NIL;
  311.   struct hostent *host_name;
  312.   void *tcpstream;
  313.   SMTPSTREAM *nstream = NIL;
  314.                 /* return prototype for OP_PROTOTYPE call */
  315.   if (!stream) return &nntpproto;
  316.   mail_valid_net_parse (stream->mailbox,&mb);
  317.   if (!lhostn) {        /* have local host yet? */
  318.     gethostname(tmp,MAILTMPLEN);/* get local host name */
  319.     lhostn = cpystr ((host_name = gethostbyname (tmp)) ?
  320.              host_name->h_name : tmp);
  321.   }
  322.   if (!*mb.mailbox) strcpy (mb.mailbox,"general");
  323.   if (LOCAL) {            /* if recycle stream, see if changing hosts */
  324.     if (strcmp (lcase (mb.host),lcase (strcpy (tmp,LOCAL->host)))) {
  325.       sprintf (tmp,"Closing connection to %s",LOCAL->host);
  326.       if (!stream->silent) mm_log (tmp,(long) NIL);
  327.     }
  328.     else {            /* same host, preserve NNTP connection */
  329.       sprintf (tmp,"Reusing connection to %s",LOCAL->host);
  330.       if (!stream->silent) mm_log (tmp,(long) NIL);
  331.       nstream = LOCAL->nntpstream;
  332.       LOCAL->nntpstream = NIL;    /* keep nntp_close() from punting it */
  333.     }
  334.     nntp_close (stream);    /* do close action */
  335.     stream->dtb = &nntpdriver;/* reattach this driver */
  336.     mail_free_cache (stream);    /* clean up cache */
  337.   }
  338.  
  339.                 /* open NNTP now if not already open */
  340.   if (!nstream && (tcpstream = tcp_open (mb.host,mb.port ?
  341.                      (long) mb.port : NNTPTCPPORT))) {
  342.     nstream = (SMTPSTREAM *) fs_get (sizeof (SMTPSTREAM));
  343.     nstream->tcpstream = tcpstream;
  344.     nstream->debug = stream->debug;
  345.     nstream->reply = NIL;
  346.                 /* get NNTP greeting */
  347.     if (smtp_reply (nstream) == NNTPGREET)
  348.       mm_log (nstream->reply + 4,(long) NIL);
  349.     else {            /* oops */
  350.       mm_log (nstream->reply,ERROR);
  351.       smtp_close (nstream);    /* punt stream */
  352.       nstream = NIL;
  353.     }
  354.   }
  355.   if (nstream) {        /* now try to open newsgroup */
  356.     if ((!stream->halfopen) &&    /* open the newsgroup if not halfopen */
  357.     ((smtp_send (nstream,"GROUP",mb.mailbox) != NNTPGOK) ||
  358.      ((nmsgs = strtol (nstream->reply + 4,&s,10)) < 0) ||
  359.      ((i = strtol (s,&s,10)) < 0) || ((j = strtol (s,&s,10)) < 0))) {
  360.       mm_log (nstream->reply,ERROR);
  361.       smtp_close (nstream);    /* punt stream */
  362.       nstream = NIL;
  363.       return NIL;
  364.     }
  365.                 /* newsgroup open, instantiate local data */
  366.     stream->local = fs_get (sizeof (NNTPLOCAL));
  367.     LOCAL->nntpstream = nstream;
  368.     LOCAL->dirty = NIL;        /* no update to .newsrc needed yet */
  369.                 /* copy host and newsgroup name */
  370.     LOCAL->host = cpystr (mb.host);
  371.     LOCAL->name = cpystr (mb.mailbox);
  372.     stream->sequence++;        /* bump sequence number */
  373.     stream->readonly = T;    /* make sure higher level knows readonly */
  374.     if (stream->halfopen) {    /* no caches or buffers for half-open */
  375.       LOCAL->number = NIL;
  376.       LOCAL->header = LOCAL->body = NIL;
  377.       LOCAL->seen = NIL;
  378.       LOCAL->buf = NIL;
  379.     }
  380.  
  381.     else {            /* try to get list of valid numbers */
  382.                 /* make temporary buffer */
  383.       LOCAL->buf = (char *) fs_get ((LOCAL->buflen = MAXMESSAGESIZE) + 1);
  384.       if (smtp_send (nstream,"LISTGROUP",mb.mailbox) == NNTPGOK) {
  385.                 /* create number map */
  386.     LOCAL->number = (unsigned long *) fs_get(nmsgs*sizeof (unsigned long));
  387.                 /* initialize c-client/NNTP map */
  388.     for (i = 0; (i<nmsgs) && (s = tcp_getline (nstream->tcpstream)); ++i) {
  389.       LOCAL->number[i] = atol (s);
  390.       fs_give ((void **) &s);
  391.     }
  392.                 /* get and flush the dot-line */
  393.     if ((s = tcp_getline (nstream->tcpstream)) && (*s == '.'))
  394.       fs_give ((void **) &s);
  395.     else {            /* lose */
  396.       mm_log ("NNTP article listing protocol failure",ERROR);
  397.       nntp_close (stream);    /* do close action */
  398.     }
  399.       }
  400.       else {            /* a vanilla NNTP server, barf */
  401.                 /* any holes in sequence? */
  402.     if (nmsgs != (k = (j - i) + 1)) {
  403.       sprintf (tmp,"[HOLES] %ld non-existant message(s)",k-nmsgs);
  404.       mm_notify (stream,tmp,(long) NIL);
  405.       nmsgs = k;        /* sure are, set new message count */
  406.     }
  407.                 /* create number map */
  408.     LOCAL->number = (unsigned long *) fs_get(nmsgs*sizeof (unsigned long));
  409.                 /* initialize c-client/NNTP map */
  410.     for (k = 0; k < nmsgs; ++k) LOCAL->number[k] = i + k;
  411.       }
  412.                 /* create caches */
  413.       LOCAL->header = (char **) fs_get (nmsgs * sizeof (char *));
  414.       LOCAL->body = (char **) fs_get (nmsgs * sizeof (char *));
  415.       LOCAL->seen = (char *) fs_get (nmsgs * sizeof (char));
  416.                 /* initialize per-message cache */
  417.       for (i = 0; i < nmsgs; ++i) {
  418.     LOCAL->header[i] = LOCAL->body[i] = NIL;
  419.     LOCAL->seen[i] = NIL;
  420.       }
  421.  
  422.                 /* notify upper level that messages exist */
  423.       mail_exists (stream,nmsgs);
  424.       i = 0;            /* nothing scanned yet */
  425.       while ((t = news_read (&sdb)) && (s = strpbrk (t,":!")) && (c = *s)) {
  426.     *s++ = '\0';        /* tie off newsgroup name, point to data */
  427.     if (strcmp (t,LOCAL->name)) s = NIL;
  428.     else break;        /* found it! */
  429.       }
  430.       if (s) {            /* newsgroup found? */
  431.     if (c == '!') mm_log ("Not subscribed to that newsgroup",WARN);
  432.     while (*s && i < nmsgs){/* process until run out of messages or list */
  433.       j = strtol (s,&s,10);    /* start of possible range */
  434.                 /* other end of range */
  435.       k = (*s == '-') ? strtol (++s,&s,10) : j;
  436.                 /* skip messages before this range */
  437.       while ((LOCAL->number[i] < j) && (i < nmsgs)) {
  438.         if (!unseen) unseen = i + 1;
  439.         i++;
  440.       }
  441.       while ((LOCAL->number[i] >= j) && (LOCAL->number[i] <= k) &&
  442.          (i < nmsgs)){    /* mark messages within the range as seen */
  443.         LOCAL->seen[i++] = T;
  444.         mail_elt (stream,i)->deleted = T;
  445.       }
  446.       if (*s == ',') s++;    /* skip past comma */
  447.       else if (*s) {    /* better not be anything else then */
  448.         mm_log ("Bogus syntax in news state file",ERROR);
  449.         break;        /* give up fast!! */
  450.       }
  451.     }
  452.       }
  453.       else mm_log ("No state for newsgroup found, reading as new",WARN);
  454.       if (t) fs_give (&sdb);    /* free up database if necessary */
  455.       while (i++ < nmsgs) {    /* mark all remaining messages as new */
  456.     mail_elt (stream,i)->recent = T;
  457.     ++recent;        /* count another recent message */
  458.       }
  459.       if (unseen) {        /* report first unseen message */
  460.     sprintf (tmp,"[UNSEEN] %ld is first unseen message",unseen);
  461.     mm_notify (stream,tmp,(long) NIL);
  462.       }
  463.                 /* notify upper level about recent */
  464.       mail_recent (stream,recent);
  465.                 /* notify if empty bboard */
  466.       if (!(stream->nmsgs || stream->silent))
  467.     mm_log ("Newsgroup is empty",WARN);
  468.     }
  469.   }
  470.   return LOCAL ? stream : NIL;    /* if stream is alive, return to caller */
  471. }
  472.  
  473. /* NNTP mail close
  474.  * Accepts: MAIL stream
  475.  */
  476.  
  477. void nntp_close (MAILSTREAM *stream)
  478. {
  479.   if (LOCAL) {            /* only if a file is open */
  480.     nntp_check (stream);    /* dump final checkpoint */
  481.     if (LOCAL->name) fs_give ((void **) &LOCAL->name);
  482.     if (LOCAL->host) fs_give ((void **) &LOCAL->host);
  483.     nntp_gc (stream,GC_TEXTS);    /* free local cache */
  484.     if (LOCAL->number) fs_give ((void **) &LOCAL->number);
  485.     if (LOCAL->header) fs_give ((void **) &LOCAL->header);
  486.     if (LOCAL->body) fs_give ((void **) &LOCAL->body);
  487.     if (LOCAL->seen) fs_give ((void **) &LOCAL->seen);
  488.                 /* free local scratch buffer */
  489.     if (LOCAL->buf) fs_give ((void **) &LOCAL->buf);
  490.                 /* close NNTP connection */
  491.     if (LOCAL->nntpstream) smtp_close (LOCAL->nntpstream);
  492.                 /* nuke the local data */
  493.     fs_give ((void **) &stream->local);
  494.     stream->dtb = NIL;        /* log out the DTB */
  495.   }
  496. }
  497.  
  498. /* NNTP mail fetch fast information
  499.  * Accepts: MAIL stream
  500.  *        sequence
  501.  */
  502.  
  503. void nntp_fetchfast (MAILSTREAM *stream,char *sequence)
  504. {
  505.   return;            /* no-op for local mail */
  506. }
  507.  
  508.  
  509. /* NNTP mail fetch flags
  510.  * Accepts: MAIL stream
  511.  *        sequence
  512.  */
  513.  
  514. void nntp_fetchflags (MAILSTREAM *stream,char *sequence)
  515. {
  516.   return;            /* no-op for local mail */
  517. }
  518.  
  519. /* NNTP mail fetch envelope
  520.  * Accepts: MAIL stream
  521.  *        message # to fetch
  522.  *        pointer to return body
  523.  * Returns: envelope of this message, body returned in body value
  524.  *
  525.  * Fetches the "fast" information as well
  526.  */
  527.  
  528. ENVELOPE *nntp_fetchstructure (MAILSTREAM *stream,long msgno,BODY **body)
  529. {
  530.   char *h,*t;
  531.   LONGCACHE *lelt;
  532.   ENVELOPE **env;
  533.   STRING bs;
  534.   BODY **b;
  535.   MESSAGECACHE *elt = mail_elt (stream,msgno);
  536.   if (stream->scache) {        /* short cache */
  537.     if (msgno != stream->msgno){/* flush old poop if a different message */
  538.       mail_free_envelope (&stream->env);
  539.       mail_free_body (&stream->body);
  540.     }
  541.     stream->msgno = msgno;
  542.     env = &stream->env;        /* get pointers to envelope and body */
  543.     b = &stream->body;
  544.   }
  545.   else {            /* long cache */
  546.     lelt = mail_lelt (stream,msgno);
  547.     env = &lelt->env;        /* get pointers to envelope and body */
  548.     b = &lelt->body;
  549.   }
  550.   if ((body && !*b) || !*env) {    /* have the poop we need? */
  551.     mail_free_envelope (env);    /* flush old envelope and body */
  552.     mail_free_body (b);
  553.     h = nntp_fetchheader (stream,msgno);
  554.     t = body ? nntp_fetchtext_work (stream,msgno) : "dummy";
  555.                 /* calculate message size */
  556.     elt->rfc822_size = strlen (h) + strlen (t);
  557.     INIT (&bs,mail_string,(void *) t,strlen (t));
  558.                 /* parse envelope and body */
  559.     rfc822_parse_msg (env,body ? b : NIL,h,strlen (h),&bs,lhostn,LOCAL->buf);
  560.                 /* parse date */
  561.     if (*env && (*env)->date) mail_parse_date (elt,(*env)->date);
  562.     if (!elt->month) mail_parse_date (elt,"01-JAN-1969 00:00:00 GMT");
  563.   }
  564.   if (body) *body = *b;        /* return the body */
  565.   return *env;            /* return the envelope */
  566. }
  567.  
  568. /* NNTP mail fetch message header
  569.  * Accepts: MAIL stream
  570.  *        message # to fetch
  571.  * Returns: message header in RFC822 format
  572.  */
  573.  
  574. char *nntp_fetchheader (MAILSTREAM *stream,long msgno)
  575. {
  576.   char tmp[MAILTMPLEN];
  577.   long m = msgno - 1;
  578.   if (!LOCAL->header[m]) {    /* fetch header if don't have already */
  579.     sprintf (tmp,"%ld",LOCAL->number[m]);
  580.     if (smtp_send (LOCAL->nntpstream,"HEAD",tmp) == NNTPHEAD)
  581.       LOCAL->header[m] = nntp_slurp (stream);
  582.     else {            /* failed, mark as deleted */
  583.       LOCAL->seen[m] = T;
  584.       mail_elt (stream,msgno)->deleted = T;
  585.     }
  586.   }
  587.   return LOCAL->header[m] ? LOCAL->header[m] : "";
  588. }
  589.  
  590.  
  591. /* NNTP mail fetch message text (body only)
  592.  * Accepts: MAIL stream
  593.  *        message # to fetch
  594.  * Returns: message text in RFC822 format
  595.  */
  596.  
  597. char *nntp_fetchtext (MAILSTREAM *stream,long msgno)
  598. {
  599.   long m = msgno - 1;
  600.   MESSAGECACHE *elt = mail_elt (stream,msgno);
  601.   elt->seen = T;        /* mark as seen */
  602.   return nntp_fetchtext_work (stream,msgno);
  603. }
  604.  
  605.  
  606. /* NNTP mail fetch message text work
  607.  * Accepts: MAIL stream
  608.  *        message # to fetch
  609.  * Returns: message text in RFC822 format
  610.  */
  611.  
  612. char *nntp_fetchtext_work (MAILSTREAM *stream,long msgno)
  613. {
  614.   char tmp[MAILTMPLEN];
  615.   long m = msgno - 1;
  616.   if (!LOCAL->body[m]) {    /* fetch body if don't have already */
  617.     sprintf (tmp,"%ld",LOCAL->number[m]);
  618.     if (smtp_send (LOCAL->nntpstream,"BODY",tmp) == NNTPBODY)
  619.       LOCAL->body[m] = nntp_slurp (stream);
  620.     else {            /* failed, mark as deleted */
  621.       LOCAL->seen[m] = T;
  622.       mail_elt (stream,msgno)->deleted = T;
  623.     }
  624.   }
  625.   return LOCAL->body[m] ? LOCAL->body[m] : "";
  626. }
  627.  
  628. /* NNTP mail slurp NNTP dot-terminated text
  629.  * Accepts: MAIL stream
  630.  * Returns: text
  631.  */
  632.  
  633. char *nntp_slurp (MAILSTREAM *stream)
  634. {
  635.   char *s,*t;
  636.   unsigned long i;
  637.   unsigned long bufpos = 0;
  638.   while (s = tcp_getline (LOCAL->nntpstream->tcpstream)) {
  639.     if (*s == '.') {        /* possible end of text? */
  640.       if (s[1]) t = s + 1;    /* pointer to true start of line */
  641.       else break;        /* end of data */
  642.     }
  643.     else t = s;            /* want the entire line */
  644.                 /* ensure have enough room */
  645.     if (LOCAL->buflen < (bufpos + (i = strlen (t)) + 5))
  646.       fs_resize ((void **) &LOCAL->buf,LOCAL->buflen += (MAXMESSAGESIZE + 1));
  647.                 /* copy the text */
  648.     strncpy (LOCAL->buf + bufpos,t,i);
  649.     bufpos += i;        /* set new buffer position */
  650.     LOCAL->buf[bufpos++] = '\015';
  651.     LOCAL->buf[bufpos++] = '\012';
  652.     fs_give ((void **) &s);    /* free the line */
  653.   }
  654.   LOCAL->buf[bufpos++] = '\015';/* add final newline */
  655.   LOCAL->buf[bufpos++] = '\012';
  656.   LOCAL->buf[bufpos++] = '\0';    /* tie off string with NUL */
  657.   return cpystr (LOCAL->buf);    /* return copy of collected string */
  658. }
  659.  
  660. /* NNTP fetch message body as a structure
  661.  * Accepts: Mail stream
  662.  *        message # to fetch
  663.  *        section specifier
  664.  *        pointer to length
  665.  * Returns: pointer to section of message body
  666.  */
  667.  
  668. char *nntp_fetchbody (MAILSTREAM *stream,long m,char *s,unsigned long *len)
  669. {
  670.   BODY *b;
  671.   PART *pt;
  672.   unsigned long i;
  673.   char *base;
  674.   unsigned long offset = 0;
  675.   MESSAGECACHE *elt = mail_elt (stream,m);
  676.                 /* make sure have a body */
  677.   if (!(nntp_fetchstructure (stream,m,&b) && b && s && *s &&
  678.     ((i = strtol (s,&s,10)) > 0) &&
  679.     (base = nntp_fetchtext_work (stream,m))))
  680.     return NIL;
  681.   do {                /* until find desired body part */
  682.                 /* multipart content? */
  683.     if (b->type == TYPEMULTIPART) {
  684.       pt = b->contents.part;    /* yes, find desired part */
  685.       while (--i && (pt = pt->next));
  686.       if (!pt) return NIL;    /* bad specifier */
  687.                 /* note new body, check valid nesting */
  688.       if (((b = &pt->body)->type == TYPEMULTIPART) && !*s) return NIL;
  689.       offset = pt->offset;    /* get new offset */
  690.     }
  691.     else if (i != 1) return NIL;/* otherwise must be section 1 */
  692.                 /* need to go down further? */
  693.     if (i = *s) switch (b->type) {
  694.     case TYPEMESSAGE:        /* embedded message, calculate new base */
  695.       offset = b->contents.msg.offset;
  696.       b = b->contents.msg.body;    /* get its body, drop into multipart case */
  697.     case TYPEMULTIPART:        /* multipart, get next section */
  698.       if ((*s++ == '.') && (i = strtol (s,&s,10)) > 0) break;
  699.     default:            /* bogus subpart specification */
  700.       return NIL;
  701.     }
  702.   } while (i);
  703.                 /* lose if body bogus */
  704.   if ((!b) || b->type == TYPEMULTIPART) return NIL;
  705.   elt->seen = T;        /* mark as seen */
  706.   return rfc822_contents (&LOCAL->buf,&LOCAL->buflen,len,base + offset,
  707.               b->size.ibytes,b->encoding);
  708. }
  709.  
  710. /* NNTP mail set flag
  711.  * Accepts: MAIL stream
  712.  *        sequence
  713.  *        flag(s)
  714.  */
  715.  
  716. void nntp_setflag (MAILSTREAM *stream,char *sequence,char *flag)
  717. {
  718.   MESSAGECACHE *elt;
  719.   long i;
  720.   short f = nntp_getflags (stream,flag);
  721.   if (!f) return;        /* no-op if no flags to modify */
  722.                 /* get sequence and loop on it */
  723.   if (mail_sequence (stream,sequence)) for (i = 0; i < stream->nmsgs; i++)
  724.     if ((elt = mail_elt (stream,i + 1))->sequence) {
  725.       if (f&fSEEN) elt->seen=T;    /* set all requested flags */
  726.       if (f&fDELETED) {        /* deletion also purges the cache */
  727.     elt->deleted = T;    /* mark deleted */
  728.     if (LOCAL->header[i]) fs_give ((void **) &LOCAL->header[i]);
  729.     if (LOCAL->body[i]) fs_give ((void **) &LOCAL->body[i]);
  730.     if (!LOCAL->seen[i]) LOCAL->seen[i] = LOCAL->dirty = T;
  731.       }
  732.       if (f&fFLAGGED) elt->flagged = T;
  733.       if (f&fANSWERED) elt->answered = T;
  734.     }
  735. }
  736.  
  737.  
  738. /* NNTP mail clear flag
  739.  * Accepts: MAIL stream
  740.  *        sequence
  741.  *        flag(s)
  742.  */
  743.  
  744. void nntp_clearflag (MAILSTREAM *stream,char *sequence,char *flag)
  745. {
  746.   MESSAGECACHE *elt;
  747.   long i;
  748.   short f = nntp_getflags (stream,flag);
  749.   if (!f) return;        /* no-op if no flags to modify */
  750.                 /* get sequence and loop on it */
  751.   if (mail_sequence (stream,sequence)) for (i = 0; i < stream->nmsgs; i++)
  752.     if ((elt = mail_elt (stream,i + 1))->sequence) {
  753.                 /* clear all requested flags */
  754.       if (f&fSEEN) elt->seen = NIL;
  755.       if (f&fDELETED) {
  756.     elt->deleted = NIL;    /* undelete */
  757.     if (LOCAL->seen[i]) {    /* if marked in newsrc */
  758.       LOCAL->seen[i] = NIL;    /* unmark it now */
  759.       LOCAL->dirty = T;    /* mark stream as dirty */
  760.     }
  761.       }
  762.       if (f&fFLAGGED) elt->flagged = NIL;
  763.       if (f&fANSWERED) elt->answered = NIL;
  764.     }
  765. }
  766.  
  767. /* NNTP mail search for messages
  768.  * Accepts: MAIL stream
  769.  *        search criteria
  770.  */
  771.  
  772. void nntp_search (MAILSTREAM *stream,char *criteria)
  773. {
  774.   long i,n;
  775.   char *d;
  776.   search_t f;
  777.                 /* initially all searched */
  778.   for (i = 1; i <= stream->nmsgs; ++i) mail_elt (stream,i)->searched = T;
  779.                 /* get first criterion */
  780.   if (criteria && (criteria = strtok (criteria," "))) {
  781.                 /* for each criterion */
  782.     for (; criteria; (criteria = strtok (NIL," "))) {
  783.       f = NIL; d = NIL; n = 0;    /* init then scan the criterion */
  784.       switch (*ucase (criteria)) {
  785.       case 'A':            /* possible ALL, ANSWERED */
  786.     if (!strcmp (criteria+1,"LL")) f = nntp_search_all;
  787.     else if (!strcmp (criteria+1,"NSWERED")) f = nntp_search_answered;
  788.     break;
  789.       case 'B':            /* possible BCC, BEFORE, BODY */
  790.     if (!strcmp (criteria+1,"CC"))
  791.       f = nntp_search_string (nntp_search_bcc,&d,&n);
  792.     else if (!strcmp (criteria+1,"EFORE"))
  793.       f = nntp_search_date (nntp_search_before,&n);
  794.     else if (!strcmp (criteria+1,"ODY"))
  795.       f = nntp_search_string (nntp_search_body,&d,&n);
  796.     break;
  797.       case 'C':            /* possible CC */
  798.     if (!strcmp (criteria+1,"C")) 
  799.       f = nntp_search_string (nntp_search_cc,&d,&n);
  800.     break;
  801.       case 'D':            /* possible DELETED */
  802.     if (!strcmp (criteria+1,"ELETED")) f = nntp_search_deleted;
  803.     break;
  804.       case 'F':            /* possible FLAGGED, FROM */
  805.     if (!strcmp (criteria+1,"LAGGED")) f = nntp_search_flagged;
  806.     else if (!strcmp (criteria+1,"ROM"))
  807.       f = nntp_search_string (nntp_search_from,&d,&n);
  808.     break;
  809.       case 'K':            /* possible KEYWORD */
  810.     if (!strcmp (criteria+1,"EYWORD"))
  811.       f = nntp_search_flag (nntp_search_keyword,&d);
  812.     break;
  813.       case 'N':            /* possible NEW */
  814.     if (!strcmp (criteria+1,"EW")) f = nntp_search_new;
  815.     break;
  816.  
  817.       case 'O':            /* possible OLD, ON */
  818.     if (!strcmp (criteria+1,"LD")) f = nntp_search_old;
  819.     else if (!strcmp (criteria+1,"N"))
  820.       f = nntp_search_date (nntp_search_on,&n);
  821.     break;
  822.       case 'R':            /* possible RECENT */
  823.     if (!strcmp (criteria+1,"ECENT")) f = nntp_search_recent;
  824.     break;
  825.       case 'S':            /* possible SEEN, SINCE, SUBJECT */
  826.     if (!strcmp (criteria+1,"EEN")) f = nntp_search_seen;
  827.     else if (!strcmp (criteria+1,"INCE"))
  828.       f = nntp_search_date (nntp_search_since,&n);
  829.     else if (!strcmp (criteria+1,"UBJECT"))
  830.       f = nntp_search_string (nntp_search_subject,&d,&n);
  831.     break;
  832.       case 'T':            /* possible TEXT, TO */
  833.     if (!strcmp (criteria+1,"EXT"))
  834.       f = nntp_search_string (nntp_search_text,&d,&n);
  835.     else if (!strcmp (criteria+1,"O"))
  836.       f = nntp_search_string (nntp_search_to,&d,&n);
  837.     break;
  838.       case 'U':            /* possible UN* */
  839.     if (criteria[1] == 'N') {
  840.       if (!strcmp (criteria+2,"ANSWERED")) f = nntp_search_unanswered;
  841.       else if (!strcmp (criteria+2,"DELETED")) f = nntp_search_undeleted;
  842.       else if (!strcmp (criteria+2,"FLAGGED")) f = nntp_search_unflagged;
  843.       else if (!strcmp (criteria+2,"KEYWORD"))
  844.         f = nntp_search_flag (nntp_search_unkeyword,&d);
  845.       else if (!strcmp (criteria+2,"SEEN")) f = nntp_search_unseen;
  846.     }
  847.     break;
  848.       default:            /* we will barf below */
  849.     break;
  850.       }
  851.       if (!f) {            /* if can't determine any criteria */
  852.     sprintf (LOCAL->buf,"Unknown search criterion: %.80s",criteria);
  853.     mm_log (LOCAL->buf,ERROR);
  854.     return;
  855.       }
  856.                 /* run the search criterion */
  857.       for (i = 1; i <= stream->nmsgs; ++i)
  858.     if (mail_elt (stream,i)->searched && !(*f) (stream,i,d,n))
  859.       mail_elt (stream,i)->searched = NIL;
  860.     }
  861.                 /* report search results to main program */
  862.     for (i = 1; i <= stream->nmsgs; ++i)
  863.       if (mail_elt (stream,i)->searched) mail_searched (stream,i);
  864.   }
  865. }
  866.  
  867. /* NNTP mail ping mailbox
  868.  * Accepts: MAIL stream
  869.  * Returns: T if stream alive, else NIL
  870.  */
  871.  
  872. long nntp_ping (MAILSTREAM *stream)
  873. {
  874.   /* Kludge alert: SMTPSOFTFATAL is 421 which is used in NNTP to mean ``No
  875.    * next article in this group''.  Hopefully, no NNTP server will choke on
  876.    * a bogus command. */
  877.   return (smtp_send (LOCAL->nntpstream,"PING","PONG") != SMTPSOFTFATAL);
  878. }
  879.  
  880.  
  881. /* NNTP mail check mailbox
  882.  * Accepts: MAIL stream
  883.  */
  884.  
  885. void nntp_check (MAILSTREAM *stream)
  886. {
  887.   int fd;
  888.   long i,j,k;
  889.   char *s;
  890.   char tmp[MAILTMPLEN];
  891.   struct stat sbuf;
  892.   struct iovec iov[3];
  893.   if (!LOCAL->dirty) return;    /* never do if no updates */
  894.   *LOCAL->buf = '\n';        /* header to make for easier searches */
  895.                 /* open .newsrc file */
  896.   if ((fd = open (NEWSRC,O_RDWR|O_CREAT,0600)) < 0) {
  897.     mm_log ("Can't update news state",ERROR);
  898.     return;
  899.   }
  900.   flock (fd,LOCK_EX);        /* wait for exclusive access */
  901.   fstat (fd,&sbuf);        /* get size of data */
  902.                 /* ensure enough room */
  903.   if (sbuf.st_size >= (LOCAL->buflen + 1)) {
  904.                 /* fs_resize does an unnecessary copy */
  905.     fs_give ((void **) &LOCAL->buf);
  906.     LOCAL->buf = (char *) fs_get ((LOCAL->buflen = sbuf.st_size + 1) + 1);
  907.   }
  908.   *LOCAL->buf = '\n';        /* slurp the silly thing in */
  909.   read (fd,iov[0].iov_base = LOCAL->buf + 1,iov[0].iov_len = sbuf.st_size);
  910.                 /* tie off file */
  911.   LOCAL->buf[sbuf.st_size + 1] = '\0';
  912.                 /* make backup file */
  913.   strcat (strcpy (tmp,myhomedir ()),"/.oldnewsrc");
  914.   if ((i = open (tmp,O_WRONLY|O_CREAT,0600)) >= 0) {
  915.     write (i,LOCAL->buf + 1,sbuf.st_size);
  916.     close (i);
  917.   }
  918.  
  919.                 /* find as subscribed newsgroup */
  920.   sprintf (tmp,"\n%s:",LOCAL->name);
  921.   if (s = strstr (LOCAL->buf,tmp)) s += strlen (tmp);
  922.   else {            /* find as unsubscribed newsgroup */
  923.     sprintf (tmp,"\n%s!",LOCAL->name);
  924.     if (s = strstr (LOCAL->buf,tmp)) s += strlen (tmp);
  925.   }
  926.   iov[2].iov_base = "";        /* dummy in case no third block */
  927.   iov[2].iov_len = 0;
  928.   if (s) {            /* found existing, calculate prefix length */
  929.     iov[0].iov_len = s - (LOCAL->buf + 1);
  930.     if (s = strchr (s+1,'\n')) {/* find suffix */
  931.       iov[2].iov_base = ++s;    /* suffix base and length */
  932.       iov[2].iov_len = sbuf.st_size - (s - (LOCAL->buf + 1));
  933.     }
  934.     s = tmp;            /* pointer to dump sequence numbers */
  935.   }
  936.   else {            /* not found, append as unsubscribed group */
  937.     sprintf (tmp,"%s!",LOCAL->name);
  938.     s = tmp + strlen (tmp);    /* point to end of string */
  939.   }
  940.   *s++ = ' ';            /* leading space */
  941.   *s = '\0';            /* go through list */
  942.   for (i = 0,j = 1,k = 0; i < stream->nmsgs; ++i) {
  943.     if (LOCAL->seen[i]) {    /* seen message? */
  944.       k = LOCAL->number[i];    /* this is the top of the current range */
  945.       if (j == 0) j = k;    /* if no range in progress, start one */
  946.     }
  947.     else if (j != 0) {        /* unread message, ending a range */
  948.                 /* calculate end of range */
  949.       if (k = LOCAL->number[i] - 1) {
  950.                 /* dump range */
  951.     sprintf (s,(j == k) ? "%d," : "%d-%d,",j,k);
  952.     s += strlen (s);    /* find end of string */
  953.       }
  954.       j = 0;            /* no more range in progress */
  955.     }
  956.   }
  957.   if (j) {            /* dump trailing range */
  958.     sprintf (s,(j == k) ? "%d" : "%d-%d",j,k);
  959.     s += strlen (s);        /* find end of string */
  960.   }
  961.   else if (s[-1] == ',') s--;    /* prepare to patch out any trailing comma */
  962.   *s++ = '\n';            /* trailing newline */
  963.   iov[1].iov_base = tmp;    /* this group text */
  964.   iov[1].iov_len = s - tmp;    /* length of the text */
  965.   lseek (fd,0,L_SET);        /* go to beginning of file */
  966.   writev (fd,iov,iov[2].iov_len ? 3 : 2);
  967.   ftruncate (fd,iov[0].iov_len + iov[1].iov_len + iov[2].iov_len);
  968.   flock (fd,LOCK_UN);        /* unlock the file */
  969.   close (fd);            /* flush .newsrc file */
  970. }
  971.  
  972. /* NNTP mail expunge mailbox
  973.  * Accepts: MAIL stream
  974.  */
  975.  
  976. void nntp_expunge (MAILSTREAM *stream)
  977. {
  978.   if (!stream->silent) mm_log ("Expunge ignored on readonly mailbox",NIL);
  979. }
  980.  
  981.  
  982. /* NNTP mail copy message(s)
  983.  * Accepts: MAIL stream
  984.  *        sequence
  985.  *        destination mailbox
  986.  * Returns: T if copy successful, else NIL
  987.  */
  988.  
  989. long nntp_copy (MAILSTREAM *stream,char *sequence,char *mailbox)
  990. {
  991.   char tmp[MAILTMPLEN],lock[MAILTMPLEN];
  992.   struct stat sbuf;
  993.   char c,*h,*t,*s,*s1;
  994.   int fd;
  995.   long i,m;
  996.   long ret = T;
  997.   MESSAGECACHE *elt;
  998.                 /* get sequence to do */
  999.   if (!mail_sequence (stream,sequence)) return NIL;
  1000.                 /* get destination mailbox */
  1001.   if ((fd = bezerk_lock (bezerk_file (tmp,mailbox),O_WRONLY|O_APPEND|O_CREAT,
  1002.               S_IREAD|S_IWRITE,lock,LOCK_EX)) < 0) {
  1003.     sprintf (LOCAL->buf,"Can't open destination mailbox: %s",strerror (errno));
  1004.     mm_log (LOCAL->buf,ERROR);
  1005.     return NIL;
  1006.   }
  1007.   mm_critical (stream);        /* go critical */
  1008.   fstat (fd,&sbuf);        /* get current file size */
  1009.  
  1010.                 /* write all requested messages to mailbox */
  1011.   for (m = 1; ret && (m <= stream->nmsgs); m++)
  1012.     if ((elt = mail_elt (stream,m))->sequence) {
  1013.                 /* get message header and text sans CR */
  1014.       h = cpystr (nntp_fetchheader (stream,m));
  1015.       for (s = h,s1 = h; c = *s++;) if (c != '\015') *s1++ = c;
  1016.       *s1++ = '\0';        /* tie off header string */
  1017.       t = cpystr (nntp_fetchtext (stream,m));
  1018.       for (s = t,s1 = t; c = *s++;) if (c != '\015') *s1++ = c;
  1019.       *s1++ = '\0';        /* tie off text string */
  1020.                 /* make sure we have a date */
  1021.       nntp_fetchstructure (stream,m,NIL);
  1022.                 /* build From header line */
  1023.       strcpy (tmp,"From somebody ");
  1024.                 /* use Path: contents if have it */
  1025.       if (((*(s1 = h) == 'P' && h[1] == 'a' && h[2] == 't' && h[3] == 'h' &&
  1026.         h[4] == ':' && h[5] == ' ') || (s1 = strstr (h,"\nPath: "))) &&
  1027.       (s = strchr (s1 += 6,'\n'))) {
  1028.     strncpy (tmp+5,s1,i = s - s1);
  1029.     tmp[5 + i] = ' ';
  1030.     s = tmp + 6 + i;    /* where to write date */
  1031.       }
  1032.       else s = tmp + strlen (tmp);
  1033.       mail_cdate (s,elt);    /* write date */
  1034.       sprintf (tmp + strlen (tmp),"Article %lu of %s\n",
  1035.            LOCAL->number[m - 1],LOCAL->name);
  1036.       if ((write (fd,tmp,strlen (tmp)) < 0) || (write (fd,h,strlen (h)) < 0) ||
  1037.       (write (fd,t,strlen (t)) < 0) || (write (fd,"\n",1) < 0)) {
  1038.     sprintf (LOCAL->buf,"Message %ld copy failed: %s",m,strerror (errno));
  1039.     mm_log (LOCAL->buf,ERROR);
  1040.     ftruncate (fd,sbuf.st_size);
  1041.     ret = NIL;        /* give up */
  1042.       }
  1043.       fs_give ((void **) &h);    /* flush strings */
  1044.       fs_give ((void **) &t);
  1045.     }
  1046.   fsync (fd);            /* force out the update */
  1047.   bezerk_unlock (fd,NIL,lock);    /* unlock and close mailbox */
  1048.   mm_nocritical (stream);    /* release critical */
  1049.   return ret;            /* return whether or not succeeded */
  1050. }
  1051.  
  1052. /* NNTP mail move message(s)
  1053.  * Accepts: MAIL stream
  1054.  *        sequence
  1055.  *        destination mailbox
  1056.  * Returns: T if move successful, else NIL
  1057.  */
  1058.  
  1059. long nntp_move (MAILSTREAM *stream,char *sequence,char *mailbox)
  1060. {
  1061.   long i;
  1062.   MESSAGECACHE *elt;
  1063.   if (!(mail_sequence (stream,sequence) &&
  1064.     nntp_copy (stream,sequence,mailbox))) return NIL;
  1065.                 /* delete all requested messages */
  1066.   for (i = 1; i <= stream->nmsgs; i++)
  1067.     if ((elt = mail_elt (stream,i))->sequence) {
  1068.       elt->deleted = T;        /* mark message deleted */
  1069.       LOCAL->dirty = T;        /* mark mailbox as dirty */
  1070.       LOCAL->seen[i - 1] = T;    /* and seen for .newsrc update */
  1071.     }
  1072.   return T;
  1073. }
  1074.  
  1075.  
  1076. /* NNTP mail append message from stringstruct
  1077.  * Accepts: MAIL stream
  1078.  *        destination mailbox
  1079.  *        stringstruct of messages to append
  1080.  * Returns: T if append successful, else NIL
  1081.  */
  1082.  
  1083. long nntp_append (MAILSTREAM *stream,char *mailbox,STRING *message)
  1084. {
  1085.   mm_log ("Append not valid for NNTP",ERROR);
  1086.   return NIL;
  1087. }
  1088.  
  1089.  
  1090. /* NNTP garbage collect stream
  1091.  * Accepts: Mail stream
  1092.  *        garbage collection flags
  1093.  */
  1094.  
  1095. void nntp_gc (MAILSTREAM *stream,long gcflags)
  1096. {
  1097.   unsigned long i;
  1098.   if (!stream->halfopen)     /* never on half-open stream */
  1099.     if (gcflags & GC_TEXTS)    /* garbage collect texts? */
  1100.                 /* flush texts from cache */
  1101.       for (i = 0; i < stream->nmsgs; i++) {
  1102.     if (LOCAL->header[i]) fs_give ((void **) &LOCAL->header[i]);
  1103.     if (LOCAL->body[i]) fs_give ((void **) &LOCAL->body[i]);
  1104.       }
  1105. }
  1106.  
  1107. /* Internal routines */
  1108.  
  1109.  
  1110. /* Parse flag list
  1111.  * Accepts: MAIL stream
  1112.  *        flag list as a character string
  1113.  * Returns: flag command list
  1114.  */
  1115.  
  1116. short nntp_getflags (MAILSTREAM *stream,char *flag)
  1117. {
  1118.   char *t;
  1119.   short f = 0;
  1120.   short i,j;
  1121.   if (flag && *flag) {        /* no-op if no flag string */
  1122.                 /* check if a list and make sure valid */
  1123.     if ((i = (*flag == '(')) ^ (flag[strlen (flag)-1] == ')')) {
  1124.       mm_log ("Bad flag list",ERROR);
  1125.       return NIL;
  1126.     }
  1127.                 /* copy the flag string w/o list construct */
  1128.     strncpy (LOCAL->buf,flag+i,(j = strlen (flag) - (2*i)));
  1129.     LOCAL->buf[j] = '\0';
  1130.     t = ucase (LOCAL->buf);    /* uppercase only from now on */
  1131.  
  1132.     while (*t) {        /* parse the flags */
  1133.       if (*t == '\\') {        /* system flag? */
  1134.     switch (*++t) {        /* dispatch based on first character */
  1135.     case 'S':        /* possible \Seen flag */
  1136.       if (t[1] == 'E' && t[2] == 'E' && t[3] == 'N') i = fSEEN;
  1137.       t += 4;        /* skip past flag name */
  1138.       break;
  1139.     case 'D':        /* possible \Deleted flag */
  1140.       if (t[1] == 'E' && t[2] == 'L' && t[3] == 'E' && t[4] == 'T' &&
  1141.           t[5] == 'E' && t[6] == 'D') i = fDELETED;
  1142.       t += 7;        /* skip past flag name */
  1143.       break;
  1144.     case 'F':        /* possible \Flagged flag */
  1145.       if (t[1] == 'L' && t[2] == 'A' && t[3] == 'G' && t[4] == 'G' &&
  1146.           t[5] == 'E' && t[6] == 'D') i = fFLAGGED;
  1147.       t += 7;        /* skip past flag name */
  1148.       break;
  1149.     case 'A':        /* possible \Answered flag */
  1150.       if (t[1] == 'N' && t[2] == 'S' && t[3] == 'W' && t[4] == 'E' &&
  1151.           t[5] == 'R' && t[6] == 'E' && t[7] == 'D') i = fANSWERED;
  1152.       t += 8;        /* skip past flag name */
  1153.       break;
  1154.     default:        /* unknown */
  1155.       i = 0;
  1156.       break;
  1157.     }
  1158.                 /* add flag to flags list */
  1159.     if (i && ((*t == '\0') || (*t++ == ' '))) f |= i;
  1160.     else {            /* bitch about bogus flag */
  1161.       mm_log ("Unknown system flag",ERROR);
  1162.       return NIL;
  1163.     }
  1164.       }
  1165.       else {            /* no user flags yet */
  1166.     mm_log ("Unknown flag",ERROR);
  1167.     return NIL;
  1168.       }
  1169.     }
  1170.   }
  1171.   return f;
  1172. }
  1173.  
  1174. /* Search support routines
  1175.  * Accepts: MAIL stream
  1176.  *        message number
  1177.  *        pointer to additional data
  1178.  *        pointer to temporary buffer
  1179.  * Returns: T if search matches, else NIL
  1180.  */
  1181.  
  1182. char nntp_search_all (MAILSTREAM *stream,long msgno,char *d,long n)
  1183. {
  1184.   return T;            /* ALL always succeeds */
  1185. }
  1186.  
  1187.  
  1188. char nntp_search_answered (MAILSTREAM *stream,long msgno,char *d,long n)
  1189. {
  1190.   return mail_elt (stream,msgno)->answered ? T : NIL;
  1191. }
  1192.  
  1193.  
  1194. char nntp_search_deleted (MAILSTREAM *stream,long msgno,char *d,long n)
  1195. {
  1196.   return mail_elt (stream,msgno)->deleted ? T : NIL;
  1197. }
  1198.  
  1199.  
  1200. char nntp_search_flagged (MAILSTREAM *stream,long msgno,char *d,long n)
  1201. {
  1202.   return mail_elt (stream,msgno)->flagged ? T : NIL;
  1203. }
  1204.  
  1205.  
  1206. char nntp_search_keyword (MAILSTREAM *stream,long msgno,char *d,long n)
  1207. {
  1208.   return NIL;            /* keywords not supported yet */
  1209. }
  1210.  
  1211.  
  1212. char nntp_search_new (MAILSTREAM *stream,long msgno,char *d,long n)
  1213. {
  1214.   MESSAGECACHE *elt = mail_elt (stream,msgno);
  1215.   return (elt->recent && !elt->seen) ? T : NIL;
  1216. }
  1217.  
  1218. char nntp_search_old (MAILSTREAM *stream,long msgno,char *d,long n)
  1219. {
  1220.   return mail_elt (stream,msgno)->recent ? NIL : T;
  1221. }
  1222.  
  1223.  
  1224. char nntp_search_recent (MAILSTREAM *stream,long msgno,char *d,long n)
  1225. {
  1226.   return mail_elt (stream,msgno)->recent ? T : NIL;
  1227. }
  1228.  
  1229.  
  1230. char nntp_search_seen (MAILSTREAM *stream,long msgno,char *d,long n)
  1231. {
  1232.   return mail_elt (stream,msgno)->seen ? T : NIL;
  1233. }
  1234.  
  1235.  
  1236. char nntp_search_unanswered (MAILSTREAM *stream,long msgno,char *d,long n)
  1237. {
  1238.   return mail_elt (stream,msgno)->answered ? NIL : T;
  1239. }
  1240.  
  1241.  
  1242. char nntp_search_undeleted (MAILSTREAM *stream,long msgno,char *d,long n)
  1243. {
  1244.   return mail_elt (stream,msgno)->deleted ? NIL : T;
  1245. }
  1246.  
  1247.  
  1248. char nntp_search_unflagged (MAILSTREAM *stream,long msgno,char *d,long n)
  1249. {
  1250.   return mail_elt (stream,msgno)->flagged ? NIL : T;
  1251. }
  1252.  
  1253.  
  1254. char nntp_search_unkeyword (MAILSTREAM *stream,long msgno,char *d,long n)
  1255. {
  1256.   return T;            /* keywords not supported yet */
  1257. }
  1258.  
  1259.  
  1260. char nntp_search_unseen (MAILSTREAM *stream,long msgno,char *d,long n)
  1261. {
  1262.   return mail_elt (stream,msgno)->seen ? NIL : T;
  1263. }
  1264.  
  1265. char nntp_search_before (MAILSTREAM *stream,long msgno,char *d,long n)
  1266. {
  1267.   return (char) (nntp_msgdate (stream,msgno) < n);
  1268. }
  1269.  
  1270.  
  1271. char nntp_search_on (MAILSTREAM *stream,long msgno,char *d,long n)
  1272. {
  1273.   return (char) (nntp_msgdate (stream,msgno) == n);
  1274. }
  1275.  
  1276.  
  1277. char nntp_search_since (MAILSTREAM *stream,long msgno,char *d,long n)
  1278. {
  1279.                 /* everybody interprets "since" as .GE. */
  1280.   return (char) (nntp_msgdate (stream,msgno) >= n);
  1281. }
  1282.  
  1283.  
  1284. unsigned long nntp_msgdate (MAILSTREAM *stream,long msgno)
  1285. {
  1286.   MESSAGECACHE *elt = mail_elt (stream,msgno);
  1287.                 /* get date if don't have it yet */
  1288.   if (!elt->day) nntp_fetchstructure (stream,msgno,NIL);
  1289.   return (long) (elt->year << 9) + (elt->month << 5) + elt->day;
  1290. }
  1291.  
  1292.  
  1293. char nntp_search_body (MAILSTREAM *stream,long msgno,char *d,long n)
  1294. {
  1295.   char *t = nntp_fetchtext_work (stream,msgno);
  1296.   return (t && search (t,(unsigned long) strlen (t),d,n));
  1297. }
  1298.  
  1299.  
  1300. char nntp_search_subject (MAILSTREAM *stream,long msgno,char *d,long n)
  1301. {
  1302.   char *t = nntp_fetchstructure (stream,msgno,NIL)->subject;
  1303.   return t ? search (t,strlen (t),d,n) : NIL;
  1304. }
  1305.  
  1306.  
  1307. char nntp_search_text (MAILSTREAM *stream,long msgno,char *d,long n)
  1308. {
  1309.   char *t = nntp_fetchheader (stream,msgno);
  1310.   return (t && search (t,strlen (t),d,n)) ||
  1311.     nntp_search_body (stream,msgno,d,n);
  1312. }
  1313.  
  1314. char nntp_search_bcc (MAILSTREAM *stream,long msgno,char *d,long n)
  1315. {
  1316.   LOCAL->buf[0] = '\0';        /* initially empty string */
  1317.                 /* get text for address */
  1318.   rfc822_write_address (LOCAL->buf,nntp_fetchstructure(stream,msgno,NIL)->bcc);
  1319.   return search (LOCAL->buf,strlen (LOCAL->buf),d,n);
  1320. }
  1321.  
  1322.  
  1323. char nntp_search_cc (MAILSTREAM *stream,long msgno,char *d,long n)
  1324. {
  1325.   LOCAL->buf[0] = '\0';        /* initially empty string */
  1326.                 /* get text for address */
  1327.   rfc822_write_address (LOCAL->buf,nntp_fetchstructure (stream,msgno,NIL)->cc);
  1328.   return search (LOCAL->buf,strlen (LOCAL->buf),d,n);
  1329. }
  1330.  
  1331.  
  1332. char nntp_search_from (MAILSTREAM *stream,long msgno,char *d,long n)
  1333. {
  1334.   LOCAL->buf[0] = '\0';        /* initially empty string */
  1335.                 /* get text for address */
  1336.   rfc822_write_address (LOCAL->buf,
  1337.             nntp_fetchstructure (stream,msgno,NIL)->from);
  1338.   return search (LOCAL->buf,strlen (LOCAL->buf),d,n);
  1339. }
  1340.  
  1341.  
  1342. char nntp_search_to (MAILSTREAM *stream,long msgno,char *d,long n)
  1343. {
  1344.   LOCAL->buf[0] = '\0';            /* initially empty string */
  1345.                 /* get text for address */
  1346.   rfc822_write_address (LOCAL->buf,nntp_fetchstructure (stream,msgno,NIL)->to);
  1347.   return search (LOCAL->buf,strlen (LOCAL->buf),d,n);
  1348. }
  1349.  
  1350. /* Search parsers */
  1351.  
  1352.  
  1353. /* Parse a date
  1354.  * Accepts: function to return
  1355.  *        pointer to date integer to return
  1356.  * Returns: function to return
  1357.  */
  1358.  
  1359. search_t nntp_search_date (search_t f,long *n)
  1360. {
  1361.   long i;
  1362.   char *s;
  1363.   MESSAGECACHE elt;
  1364.                 /* parse the date and return fn if OK */
  1365.   return (nntp_search_string (f,&s,&i) && mail_parse_date (&elt,s) &&
  1366.       (*n = (elt.year << 9) + (elt.month << 5) + elt.day)) ? f : NIL;
  1367. }
  1368.  
  1369. /* Parse a flag
  1370.  * Accepts: function to return
  1371.  *        pointer to string to return
  1372.  * Returns: function to return
  1373.  */
  1374.  
  1375. search_t nntp_search_flag (search_t f,char **d)
  1376. {
  1377.                 /* get a keyword, return if OK */
  1378.   return (*d = strtok (NIL," ")) ? f : NIL;
  1379. }
  1380.  
  1381.  
  1382. /* Parse a string
  1383.  * Accepts: function to return
  1384.  *        pointer to string to return
  1385.  *        pointer to string length to return
  1386.  * Returns: function to return
  1387.  */
  1388.  
  1389. search_t nntp_search_string (search_t f,char **d,long *n)
  1390. {
  1391.   char *c = strtok (NIL,"");    /* remainder of criteria */
  1392.   if (c) {            /* better be an argument */
  1393.     switch (*c) {        /* see what the argument is */
  1394.     case '\0':            /* catch bogons */
  1395.     case ' ':
  1396.       return NIL;
  1397.     case '"':            /* quoted string */
  1398.       if (!(strchr (c+1,'"') && (*d = strtok (c,"\"")) && (*n = strlen (*d))))
  1399.     return NIL;
  1400.       break;
  1401.     case '{':            /* literal string */
  1402.       *n = strtol (c+1,&c,10);    /* get its length */
  1403.       if (*c++ != '}' || *c++ != '\015' || *c++ != '\012' ||
  1404.       *n > strlen (*d = c)) return NIL;
  1405.       c[*n] = '\255';        /* write new delimiter */
  1406.       strtok (c,"\255");    /* reset the strtok mechanism */
  1407.       break;
  1408.     default:            /* atomic string */
  1409.       *n = strlen (*d = strtok (c," "));
  1410.       break;
  1411.     }
  1412.     return f;
  1413.   }
  1414.   else return NIL;
  1415. }
  1416.